﻿#region File Header
//-----------------------------------------------------------------------------
// Bricks' Bane
//
// Copyright (C) 2010 Julien Villers
// This program is distributed under the terms of the 
// GNU Lesser General Public License (LGPL).
// See Docs/lgpl.txt and Docs/gpl.txt
//-----------------------------------------------------------------------------
#endregion

using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using SibLib;
using SibLib.Entities;
using SibLib.Render;
using SibLib.Render.Primitives3D;

namespace BricksBane.Entities
{
    sealed class Ball : GameplayObject, ICollideable2D
    {
        public enum E_Types
        {
            Standard = 0,
            Superball,
            Perforaball,
            Nitroball,
        };

        private static readonly Color[] COLORS =
        {
            Color.White,
            Color.Purple,
            Color.Yellow,
            Color.Red,
        };

        #region Members
        private Vector2 m_Velocity;
        private Rectangle m_BoundingBox;
        private CollisionMath.CircleLineCollisionResult m_CollisionResult;
        private E_Types m_Type;

        private static GeometricPrimitive m_Primitive = null;
        #endregion

        #region Properties
        public Vector2 Velocity
        {
            get { return m_Velocity; }
            set { m_Velocity = value; }
        }
        public Wall Wall
        {
            get;
            set;
        }
        public Paddle Paddle
        {
            get;
            set;
        }
        public Color Color
        {
            get;
            private set;
        }
        public E_Types Type
        {
            get { return m_Type; }
            set { m_Type = value; Color = COLORS[(int)value]; }
        }
        public bool IsFrozen
        {
            get;
            set;
        }
        #endregion

        #region Setup

        private static readonly Vector2 SIZE = new Vector2(12, 12);

        public Ball() :
            base()
        {
            m_BoundingBox = new Rectangle();
            Color = Color.White;
            m_CollisionResult = new CollisionMath.CircleLineCollisionResult();
        }

        public override void Initialize()
        {
            base.Initialize();

            if (m_Primitive == null)
            {
                GraphicsDevice device = GameplayScreen.Instance.GraphicsDevice;
                m_Primitive = new SpherePrimitive(device, SIZE.X, 8);
            }
        }

        #endregion

        #region Update
        public override void Update(float timeFactor)
        {
            if (IsFrozen)
                return;

            Vector2 actualVelocity, nextPosition; 
            Vector2.Multiply(ref m_Velocity, timeFactor, out actualVelocity);
            Vector2.Add(ref m_Position, ref actualVelocity, out nextPosition);

            m_BoundingBox.X = (int)(nextPosition.X - (SIZE.X / 2.0f));
            m_BoundingBox.Y = (int)(nextPosition.Y - (SIZE.Y / 2.0f));
            m_BoundingBox.Width = (int)SIZE.X;
            m_BoundingBox.Height = (int)SIZE.Y;

            bool isColliding = false;

            // Collide against limits
            Rectangle limits = Wall.GetBoundingBox();
            if (m_BoundingBox.Right > limits.Right || m_BoundingBox.Left < limits.Left)
            {
                isColliding = true;
                m_Velocity.X *= -1;
            }
            if (m_BoundingBox.Top < limits.Top)
            {
                isColliding = true;
                m_Velocity.Y *= -1;
            }
            if (m_BoundingBox.Bottom > limits.Bottom)
            {
                if (Wall.IsShieldEnabled)
                {
                    Wall.OnBallHitShield(this);
                    m_Velocity.Y *= -1;
                }
                else
                {
                    OnLost();
                }
                isColliding = true;
            }

            if (isColliding)
            {
                GameplayScreen.Instance.SoundManager.Play("Rebound");
                return;
            }

            Rectangle result;

            // Collide against paddle
            Rectangle padBox = Paddle.GetBoundingBox();
            Vector2 collisionPoint;
            if (CollisionMath.LineLineIntersect(Position, new Vector2(m_BoundingBox.Center.X, m_BoundingBox.Bottom), new Vector2(padBox.Left, padBox.Top), new Vector2(padBox.Right, padBox.Top), out collisionPoint))
            {
                isColliding = true;
                //Vector2 collPoint = m_CollisionResult.Point;
                float deltaX = collisionPoint.X - padBox.Center.X;
                float speed = m_Velocity.Length();
                float newAngle;
                float padHalfWidth = padBox.Width / 2.0f;
                float angleMax = MathHelper.PiOver4 / 2.0f;
                float factor = Math.Abs(deltaX) / padHalfWidth;
                if (deltaX > 0)
                    newAngle = MathHelper.Lerp(MathHelper.PiOver2, angleMax, factor);
                else
                    newAngle = MathHelper.Lerp(MathHelper.PiOver2, MathHelper.Pi - angleMax, factor);

                float speedFactor = ((factor * 0.75f) + 1.0f);
                speed *= speedFactor;     // TODO: tuning, acceleration factor
                SpeedSet(speed, newAngle);

                if (Paddle.Type == Paddle.E_Types.Glue)
                {
                    m_Position.Y = Paddle.GetBoundingBox().Top - (m_BoundingBox.Height / 2.0f);
                    IsFrozen = true;
                }

                Paddle.OnBallHit(this, collisionPoint);
            }

            if (isColliding)
            {
                GameplayScreen.Instance.SoundManager.Play("Rebound");
                return;
            }

            // Collide against bricks
            foreach (Brick brick in Wall)
            {
                // TODO: use line line collision, as with paddle.
                Rectangle brickBox = brick.GetBoundingBox();
                Rectangle.Intersect(ref m_BoundingBox, ref brickBox, out result);

                if (!result.IsEmpty)
                {
                    bool isBrickDestroyed = brick.OnBallHit(this);
                    if (!isBrickDestroyed || m_Type != E_Types.Perforaball)
                    {
                        isColliding = true;

                        CollisionMath.CircleRectangleCollide(nextPosition, SIZE.X / 2.0f, brickBox, ref m_CollisionResult);

                        if (m_CollisionResult.Normal.X != 0.0f)
                            m_Velocity.X = Math.Sign(m_CollisionResult.Normal.X) * Math.Abs(m_Velocity.X);
                        if (m_CollisionResult.Normal.Y != 0.0f)
                            m_Velocity.Y = Math.Sign(m_CollisionResult.Normal.Y) * Math.Abs(m_Velocity.Y);
                    }

                    if (isBrickDestroyed && m_Type == E_Types.Nitroball)
                        GameplayScreen.Instance.ExplodeBricks(brick, brick.Size * 1.0f);
                }
            }

            if (isColliding)
            {
                GameplayScreen.Instance.SoundManager.Play("Rebound");
                return;
            }

            m_Position = nextPosition;
        }

        #endregion

        private void OnLost()
        {
            IsActive = false;
            Wall = null;
            Paddle = null;
        }

        #region Public API

        private static readonly float H_SPEED_SLOW = 20.0f;
        private static readonly float V_SPEED_BASE = -200.0f;

        public void Spawn()
        {
            Position = Paddle.Position + new Vector2(0, -(Paddle.Size.Y / 2.0f + SIZE.Y / 2.0f));
            Velocity = new Vector2(RandomMath.RandomBetween(-H_SPEED_SLOW, H_SPEED_SLOW), V_SPEED_BASE);
            IsActive = true;
            Type = E_Types.Standard;
        }

        public void SpeedInc(bool isIncreasing)
        {
            float speed = m_Velocity.Length();
            if (isIncreasing)
                speed *= 1.2f;      // TODO: tuning
            else
                speed *= 0.8f;
            double angle = Math.Atan2(-m_Velocity.Y, m_Velocity.X);
            SpeedSet(speed, angle);
        }
        
        public void SpeedSet(float newSpeed, double angle)
        {
            GameplayScreen screen = GameplayScreen.Instance;
            float speed = MathHelper.Clamp(newSpeed, screen.BallSpeedMinimum, screen.BallSpeedMaximum);
            m_Velocity.X = (float)Math.Cos(angle) * speed;
            m_Velocity.Y = -(float)Math.Sin(angle) * speed;
        }

        #endregion

        #region Bounding Box

        public Rectangle GetBoundingBox()
        {
            return m_BoundingBox;
        }

        #endregion

        #region Render

        public override void Draw(float timeFactor)
        {
            SceneInfo info = GameplayScreen.Instance.SceneInfo;
            Matrix world = Matrix.CreateTranslation(new Vector3(Position, 0.0f));
            m_Primitive.Draw(world, info.ViewMatrix, info.ProjectionMatrix, Color);
        }

        #endregion
    }
}
